Alrighty, in this lecture, we're going to be scripting the service that will set up and handle all
of the lockers in our game.
These lockers are going to act as safe spaces that players can hide inside of, to avoid being attacked
by our Squidward AI.
The plan is, is that a player will interact with a prompt on the outside of the door, and will tween
their character to move inside of the locker, and at that point, they will only be able to see out
of these grills when they're inside of the locker.
And then there will be a prompt inside that they can use to exit the locker.
Now we have a lot of these lockers in our map, specifically in the Krusty Krab basement and then lockers
folder.
We have a whole bunch that we've placed throughout the basement, and we need to go ahead and set up
the functionality for each one of these lockers.
So we're going to use our locker service to do that.
And this is also going to act as a class for all of our lockers.
So inside of here we're going to need a few services.
One is going to be the player service.
We're going to need the server script service.
We will need replicated storage.
We're going to need server storage.
And then we're also going to need the sound service.
Now we can create the variable that will represent this module script.
We'll call it locker service.
And we'll make sure to return that at the end of our script.
And then we're also going to need to create the table that will act as the meta table for all of our
locker objects.
So locker service meta table.
And then inside of that we're going to set the underscore underscore index meta method equal to itself.
And now we can go ahead and start referencing a bunch of different events.
But first let's go ahead and grab our better tween module script.
So we can require that from server script service dot server dot modules dot better tween.
We're going to want to get the game comms event.
So that's in replicated storage dot events dot remotes dot game communication.
And we're going to get the game comms enum along with that.
So require replicated storage dot modules dot enums dot game communication.
We're going to get the camera manipulation event.
So in replicated storage the events dot remotes dot manipulate camera.
And then we can get the camera manipulation enum for this as well.
So we'll require for replicated storage dot modules dot enums dot camera manipulation enum.
We're also going to get the update guy event.
So replicated storage, dot events, dot remotes, dot update UI, and then the enum for this as well.
Guy actions enum require for replicated storage, dot modules, dot enums dot guy actions enum.
We're also going to reference two sounds inside of replicated storage, one sound we can call Lock or
Open Sound, and we'll play this to go ahead and open the locker.
So that's in our instances folder.
Dot sounds dot locker open.
And then we can get our locker close sound.
So replicated storage dot instances dot sounds dot locker close.
And then we're going to have a table to act as a cache to store all of the current lockers in the game.
And then we're going to define a constant that will represent how long we want to tween the door opening
and closing for.
So we'll call it door move duration.
And we'll set it to something like 0.6 seconds.
I found that to be pretty good.
And then from this point, we're going to need quite a few private functions.
One function we can create is to play a sound, and we'll use the game comms event to play the sound
to players.
But I wanted to create a private function.
That way we only need to type out the function itself without having to, you know, use the game comms
event and fire to all the clients and fill all that stuff out.
It can get a little bit long.
So we could pass to this function a sound instance, which is a sound.
We can pass the parent for the part, which is a bass part, and we'll get the position from this parent
and set that for the sound.
We're going to need a function to open and then close a door on our locker.
And the reason we need to do this is for example, the player wants to enter or leave the locker, the
door is going to open, and then the door is going to close, right.
So we'll have that as well.
And then we'll have a local function to listen for when a prompt gets triggered.
So we'll call on prompt triggered.
And this will listen to when different you know prompts inside of our lockers get triggered afterwards
we can go ahead and create the function that will act as the constructor for our locker service.
So function locker service dot new.
We'll pass a model for the locker.
We're also going to need a function inside of the locker service meta table for the objects themselves.
So on specific lockers, we want to have the ability to set the occupant.
And we'll pass a character model to this.
We want to have the ability to remove an occupant.
So locker service metatable remove occupant.
We want to have the ability to enable and disable the prompts so we can have a function locker service
meta table enable prompt.
And then we can also do function locker service meta table disable prompt.
And the only function we're going to need for our service itself is a start function.
So function locker service start.
And this is where we'll loop through every single locker model that is inside of the workspace.
Krusty Krab basement lockers.
We'll get all of the children in there, and we'll use our constructor and locker service dot new and
pass the locker model.
So let's go ahead and fill out our constructor.
Inside of here we can define a table.
We'll call it self.
And then inside of this table, we'll have a property to refer to the locker model itself, which is
equal to the locker model self dot occupant.
We're going to set to nil for now.
We're going to have a reference to the enter prompt which is equal to the locker model dot body dot
attachment dot proximity prompt.
So inside of the body there's an attachment for the entering.
And then we also have an attachment for the exit prompt when a player is inside of it.
So that's the locker model.
Get the body.
There's another attachment in there.
And that's called attachment exit.
And we'll get the proximity prompt in that as well.
And then from this point we can go ahead and set the metta table on our locker.
So locker service metta table.
And then we also want to listen for when our enter prompt is triggered.
So we will connect a function to this.
And that gets passed a player.
And we can also go ahead and denote these as proximity prompts.
So we'll listen for when it gets triggered.
And then we can call our on prompt triggered function.
And we could pass the object itself and the player that triggered it.
And then we do the exact same thing for the exit prompt that triggered we'll connect a function, get
the player, and then call our on prompt triggered function.
And we'll pass self and the player.
After that, we can go ahead and insert our object into our current lockers table.
So we'll pass self here.
And then we'll return self at the end in case we need to.
So let's go ahead and I guess fill out our on prompt triggered function.
What do we want to do when a prompt inside of our locker gets triggered.
Well first we want to get the self or the object and then the player.
And we want to ensure that this player has a character model.
So if this player does not have a character model then we're just going to return.
The next thing we need to check is whether or not the player is already in occupant of this locker.
If that's the case, then when the prompt gets triggered, we want to go ahead and remove the player
out of the locker.
So if the player character is equal to the self dot occupant, then we can call our open and close door
function.
And then we can also remove the occupant from inside our locker.
Otherwise, if there is no occupant inside of the locker, then what we could do is we first want to
ensure that the player is not already inside of, let's say, a different locker.
So let's say there are two lockers right next to each other, and the player is able to see the prompt
on the other one, and they try to activate that to move into the other locker.
Well, we're going to have problems.
So when we put a player inside of a locker we're going to set an attribute on that player.
We could call it something like inside locker and set that to either true or false.
That way we can keep track of the players in a locker.
And that way also our AI can tell whether or not a player is in a locker.
So we can have an if statement to check if this player and get an attribute like inside Locker.
So if they're already inside of a locker, then we're going to use our update guy event and fire to
this particular player here and give them a GUI actions enum small message, and we can give them some
text like you are already in a locker.
Otherwise, if there is an occupant in there and that player is not the occupant.
So if self dot occupant is truthy.
Then we need to use the update event and fire again to this particular player that this locker is currently
occupied.
So GUI actions enum dot small message and we give them some text like.
This locker is occupied.
The next thing we can go ahead and do is work on our open close door function.
So when we want to open and close the door, first off, we want to disable the prompt inside of our
locker.
So we could use that self disable prompt function.
And that means we're going to have to pass the self or the object to this, uh function here.
So we can go ahead and pass self right here.
And then the disable and enable prompt functions are actually pretty easy to do.
So we can just go down here when we want to enable the prompt we'll do self dot enter prompt dot enabled.
We'll set that to true.
Otherwise we can just set self dot enter prompt dot enabled equal to false when we want to disable it.
So we go ahead and disable that external prompt.
And then what we're going to do is we're going to set the door of the locker kankali to false.
So self dot locker model.
Dot door we're going to set can collide equal to false.
That way nobody collides with the door when it opens.
And then what we're going to do is we're going to get a tween info for how long we want to open the
door.
So we're going to do tween info dot new.
We're going to set the time as our door move duration, and then again, we can be a little fancy and
do an enemy's style like sign.
And then we're also going to ensure to store the original key frame of the lockers hinge.
So hinge dot key frame.
And then we're going to use the better tween service and tween our self dot locker dot hinge.
So inside of our locker model here we have the door.
And there's a hinge that the door is attached to.
And we rotate this hinge to you know go ahead and rotate the door outwards.
So for example if I did 90 degrees we'll open the door like that.
I think we want to swing it out a little bit more.
But.
We'll do something in a little bit here.
So we swing open the hinge, we open the door, make sure can collide is off on it.
So we pass self that locker hinge.
We pass the tween info we just created.
And then the goal is going to change the see frame of our hinge.
And that's going to be equal to the self dot locker dot hinge and the see frame.
What we can do is we can multiply this and we can do a see frame dot angles zero on the x axis.
And then we want to rotate it on the y axis.
So math dot rad we could do a little bit more than 90 degrees.
We could do like 105 degrees.
That might be good.
And then zero on the z axis.
And then after that we can use our play sound function and play like our locker open sound.
And the parent for that would be the self dot locker dot body.
So let's go ahead and fill out this function real quick as well.
All we're going to do in here is refer to our game comms event and fire to all the clients to play a
sound.
So game comms enum dot two client dot play sound.
The sound is going to be equal to the sound instance passed to the function play at point.
We're going to set that to true and the position is going to be equal to the parent dot position that
is passed to the function.
After that we can go ahead and yield for the door move duration.
And then after that is done, we need to go ahead and then close the door.
So better tween tween self dot locker dot hinge.
We'll do the same tween info here.
But instead we want to set the key frame of our hinge back to the original key frame.
And then again we can play a sound.
And this time we're going to play the locker closed sound and then pass self dot locker dot body.
After this point we can go ahead and set can collide back on our door to true.
So self dot locker dot door dot can collide is equal to true.
And oops.
Let me rename this to locker model.
Actually let me copy this.
Make sure we stay consistent here.
So this is the locker model not just the locker.
Okay, good.
And then again we're going to yield for that door move duration.
And finally, if we do not have an occupant we're going to make sure if not occupant, then we're going
to go ahead and enable the prompt inside of our object.
Now since this function is going to be yielding and we want to remove the occupant at the same time
that we're opening and closing the door, we should spawn this function in a new thread.
And then we can pass self to that function.
And that allows us to immediately call our self remove occupant function.
So let's go ahead and fill this one out.
So to go ahead and remove an occupant first we need to check if there isn't an occupant already inside
of it.
So if there is no occupant then we don't need to do anything.
Otherwise we're going to set the self dot exit prompt dot enabled equal to false.
And then we're going to get the player.
Players get player from character.
I'm going to pass self to occupant.
And then we're going to use the game comms event and fire to this particular player here.
And we can give them the game comms enum.
And we want them to stop a sound, because when we add or set an occupant inside of our locker, we're
going to be playing a breathing sound.
And when they exit or are removed from the locker, we need to stop playing that sound.
So we're going to tell them to stop a sound.
And that sound instance is inside of the sound service.
SFX Dot I believe it's called yeah breathing locker.
We want to stop that sound.
Then we can use our better tween service and we can go ahead and tween the self dot occupant dot humanoid
route part, because we're going to be moving their route part in and out of our locker.
As you can see we have a humanoid route part copy here that's on the outside of the locker.
And we also have one on the inside of the locker.
So when they want to go in, we tween their character to move in.
And then when they want to exit, we tween their character and move them to this position here.
So we're going to tween their humanoid route part.
We could do a tween info.
Dot knew of something like 0.6 seconds.
And then we're going to set the keyframe of our players character equal to the self dot locker.
Model.
And then there's a part in there called root part outside.
And then we're going to set their humanoid root part, see frame to that one, after which we can go
ahead and yield 4.6 seconds.
And then we're going to set an attribute on this player of inside locker equal to false.
And then we can do the self dot occupant and refer to the humanoid root part again.
And this is because when they're inside of the locker we're going to anchor their humanoid root part.
So to give them control back over their character we're going to set the anchor property to false.
And then we're going to go ahead and finally set occupant equal to nil.
Now we can basically do the exact same thing when we want to set an occupant.
Again we're going to set self dot occupant equal to the character passed to this function.
And then we're also going to get the player.
So players get player from character and pass the character model.
And then we're going to set an attribute on that player of inside locker equal to true.
And then we're going to go ahead and anchor their humanoid root part.
So dot anchor ID is equal to true.
And then we're going to use our better tween and tween the character dot humanoid root part.
We'll do a tween info again of 0.6 seconds, and this time we want to set the C frame equal to the self
dot locker model.
And this part is called root part inside.
And we'll set it to that C frame.
Afterwards, we can go ahead and yield for the duration of this tween, and then we're going to use
the game comms event and fire to this client to go ahead and play the breathing sound.
So player game comms enum dot two client dot play sound and the sound is equal to the sound service
dot sfx dot uh breathing locker and then play at point we're going to set to false.
And then something that's very important for us to do is we need to turn the player's camera to face
the correct way, because when they're running towards this locker and they want to go inside, their
camera's going to be facing the other side of the locker.
So what we want to do is we want to adjust the rotation of the player's camera to rotate all the way
around and face the correct direction inside of the locker.
So what we could do is we could use the camera manipulation event and fire to this particular player
that we need them to get the enum dot two client.
We want them to rotate their camera.
The tween data we can pass to this.
We could do a rotation time of like one second.
And then for the rotation we're going to set that to zero degrees on the x axis.
And then we're going to use math dot rad and refer to self dot locker model.
Get the root part that is inside and get the orientation of it around the y axis.
And then afterwards we're going to do zero on the z axis.
And the last thing we want to pass is a condition rotate around head if you remember.
And we want to set that equal to true.
That way when the camera rotates it's equivalent or equal to the offset that the camera will be when
they're in first person.
So once we do that, we can go ahead and yield for the one second that they'll be rotating their camera
around.
And then once they do that, we can go ahead and tell them to reset their camera.
So the default camera control scripts, you know, have control again.
So camera manipulation event fire client player camera manipulation enum dot two client dot reset camera.
And then finally we can set the self dot exit prompt dot enabled equal to true.
Now something that's very important is that we're actually going to have to go into our camera manipulation
handler, because there's actually a little bit of changes that I want to make for when we reset the
camera.
And when we rotate the camera around the players head.
And so in fact, I actually found a better formula for making sure that the camera is as close as possible
to the correct position when the camera is in first person mode.
So what we're going to do here is we're going to delete this.
And for the C frame that we want to set it to when we reset the camera, we're going to do C frame dot
new.
We're going to get the local player get their character and then get their head dot position right.
And now we need to offset it appropriately to match as close as possible to where the camera is located
relative to the head when they're in first person mode.
And I found that equal to a vector three dot new for the x position, we need to offset it by the local
player dot character.
We're going to get their humanoid root part so we know what direction they're facing.
We'll get the C frame of that and then get the look vector.
And we're going to get the x component of that look vector.
And then we actually need to subtract or negate this value.
So that way we move the head position back in this x direction.
Not only do we need to negate it, but we also need to multiply it by 0.5 or basically cut the value
in half, because that's what I found to be relatively close to where the player's camera position is
going to be at.
For the Y, I found it to be constant of an offset of around 0.13 studs above the player's head, and
then for the Z, we can do the exact same thing of local player dot character dot humanoid root part.
Get the C frame, get the look vector.
And this time we're going to get the z component of that look vector.
And we do not need to negate it, but we still need to multiply it or cut it in half.
And I found this C frame to be nearly almost identical to the position that the camera will be in when
they're in first person mode.
So we can copy this now, and also update that C frame to when we need to rotate our camera around the
head of the player.
So we'll go ahead and paste that right there.
So we are completed scripting inside of our locker service, and we're also completed scripting in our
camera manipulation handler.
One last change I want to make is going to be inside of the sounds handler on the client.
So go ahead and make sure you open that up.
And what we're going to be adjusting here is our fade sound out function.
Now what we want to do is we actually want to go ahead and get the original volume of the sound that
we're about to fade out, so we can pass sound volume here.
And that's because after we have faded this sound out and set the volume to zero, we never set the
volume of the sound back to its original volume.
This is a problem because if we want to play the sound again in the future, it's going to be completely
muted.
So we get the original volume, we play the scene to drown it out, and then once the sound is stopped,
we can go ahead and set the sound's volume property back to the original value so we can do sound dot
volume equal to original volume.
Great.
Now that we have done all that, we should be complete.
So now we can go ahead and test out this locker that I've copied from the basement and see what it does.
Okay.
So let's go ahead and enter our locker here.
As you can see we have our prompt that says locker enter.
So if we hold down e unfortunately it looks like nothing is happening.
So we got to figure out why exactly we are not able to enter our locker.
Oh and silly me, actually forgot that we didn't completely set up this section in our if statement,
so we're not done yet.
So when a prompt gets triggered and there's no occupant and we want to set the player to be inside of
this locker, first we need to put a return statement in here.
If the player is already in a locker.
And then afterwards we're going to spawn our open closed door function in a new thread and we'll pass
self.
And then we need to do self set occupant.
And then we need to pass our player dot character.
So we forgot to fill this section out here.
But now that we have this set up, when we activate our prompt we should be good to go.
So let me go ahead and review over the code just real quick to make sure I didn't miss anything else.
Everything is looking good so far and I don't believe I see any more issues.
So now our locker service should be working.
Okay, moment of truth.
Let's try it out again.
If we hit E, there we go.
We rotate the camera around and boom, we're inside of the locker.
As you can see, our camera rotated around and then reset the position back.
There was a little bit of a snap to move the camera back to its original position, but that's as close
as I've gotten with the math.
And if we hit E again, we should be able to exit the locker.
Perfect.
It moves us out, and then we should be able to enter back in just like that.
And I don't know if you can hear it, but if you listen carefully, you should be able to hear the breathing
sound that we've placed in there right now.
It's muffled because we're not in the basement, but it appears that everything is working correctly
with our locker system.
So in the next lecture, we're going to go ahead and script the spawning system for the basement key.
I'll go ahead and see you there.